import { Component, ContentChildren, EventEmitter, Input, Output, OnInit, OnChanges, QueryList, SimpleChanges, ViewChild } from '@angular/core';

import { DataGridConfig } from '../../../../core/config/data-grid.config';
import { BootstrapDataTableHeadComponent } from './bootstrap-data-table-head.component';
import { DataTablePageChangedEventArg } from '../shared/models/data-table-page-changed-event-arg.model';
import { DataTable } from '../shared/interfaces/data-table.interface';

/**
 * Wrapper of table. Other components with the same selector can replace its functionality.
 */
@Component({
    selector: 'op-data-table',
    templateUrl: './bootstrap-data-table.component.html'
})
export class BootstrapDataTableComponent implements OnInit, OnChanges, DataTable {
    // The BootstrapDataTableHeadComponent list used to generate the table heads.
    @ContentChildren(BootstrapDataTableHeadComponent) heads: QueryList<BootstrapDataTableHeadComponent>;

    // Paginator for access its changePage or any other methods.
    @ViewChild('paginator', { static: false }) paginator: any;

    // pageChanged event provides DataGridPageChangedEventArg to event hanlder.
    @Output() pageChanged: EventEmitter<DataTablePageChangedEventArg> = new EventEmitter<DataTablePageChangedEventArg>();

    // The original Body for table.
    @Input() body: any[];

    // The field name will be used by ngFor trackBy method.
    @Input() trackByFieldName: string;

    // The css classes will be applied to the table.
    @Input() tableClasses: string[];

    // If need to display the paginator.
    @Input() needPaginator: boolean;

    // For the paginator.
    @Input() totalRowsCount: number;

    // For the paginator. How many items will be displayed on one page.
    @Input() pageSize: number;

    // For the paginator. Array of integer values to display inside rows per page dropdown.
    @Input() pageSizeOptions: number[];

    // For the paginator. Number of page links to display.
    @Input() pageLinkSize: number;

    // Whether to hide the table head.
    @Input() hiddenHead: boolean;

    // Body for table and will be set programmatically due to server side paging or client side paging needs.
    adjustedBody: any[];

    // If cureent paging is client side. The default value is false.
    isClientSidePaging: boolean;

    // Current page index for paging.
    currentPage: number;

    // For the paginator.
    totalPageCount: number;

    // The table line height. The default is 0.6.
    lineHeight = 0.6;

    constructor() {
        // Changes the trackByKeyField method's context to BootstrapTableComponent instance.
        this.trackByKeyField = this.trackByKeyField.bind(this);

        this.initializeProperties();
    }

    ngOnInit(): void {
        // ngOnChanges will be triggered before ngOnInit.
        // That's why we moved initializeProperties to constructor.
        // this.initializeProperties();
    }

    ngOnChanges(changes: SimpleChanges) {
        // Check if current body is not null.
        if (changes.body && changes.body.currentValue) {
            if (changes.body.currentValue.length > this.pageSize) {
                // If current data source item count is greater than page size, then the client side paging will be enabled.
                this.adjustedBody = changes.body.currentValue.slice(0, this.pageSize);
                this.isClientSidePaging = true;
            } else {
                // Otherwize the server side paging is enabled.
                this.adjustedBody = changes.body.currentValue;
            }
        }
    }

    /**
     * Goes to the specific page.
     * @param pageIndex The specific page index which will jump to.
     */
    gotoPage(pageIndex: number): void {
        this.paginator.changePage(pageIndex);
    }

    /**
     * Used by trackBy of ngFor directive.
     * IMPORTANT! IMPORTANT! IMPORTANT! 
     * You have to be careful about the meaning of this in the method. It is DefaultIterableDiffer. NOT the BootstrapTableComponent as you might expect.
     * That's why you cannot access any property or method like this. It always returns undefined.
     * 
     * You have 2 options to make it work:
     * 1. Put 'this.trackByKeyField = this.trackByKeyField.bind(this)' in constructor. 
     * This changes current context to the BootstrapTableComponent as you wanted. 
     * Reference here: https://www.smashingmagazine.com/2014/01/understanding-javascript-function-prototype-bind/
     * 2. Define a fat arrow function instead. Like below:
     * trackByKeyField = (index: number, item: any): any => {
     *     return item[this.trackByFieldName].title;
     * }
     * In fat arrow function, the context points to BootstrapTableComponent directly.
     * 
     * I prefer to the first option (the bind function). Because it makes more clear to others to understand what happens and why we do it here.
     * 
     * Testing this function is a little bit tricky. If you even return a property which does not exist on item, there will be no any error at all.
     * You have to access next level property to trigger an exception. For example: return item['noSuchProperty'].text.
     * @param index The current item index in the ngFor loop.
     * @param item The current item in the ngFor loop.
     */
    trackByKeyField(index: number, item: any): any {
        return item[this.trackByFieldName];
    }

    /**
     * Calculates the total page count based on the total rows count and page size.
     */
    calculateTotalPageCount(): number {
        return Math.ceil(this.totalRowsCount / this.pageSize);
    }

    /**
     * Emits the pageChange event when page changes.
     * @param e: The page change event sent by the paginator component.
     */
    onPageChanged(e: any): void {
        // Calculates the paging related properties' value.
        this.pageSize = e.rows;
        this.currentPage = e.page + 1;
        this.totalPageCount = e.pageCount;

        // For client side paging.
        if (this.isClientSidePaging && this.body) {
            this.adjustedBody = this.body.slice(e.first, e.first + e.rows);
        }

        // Emits the pageChanged event.
        this.pageChanged.emit({
            pageStartIndex: e.first,
            pageEndIndex: e.first + e.rows,
            pageSize: this.pageSize
        });
    }

    /**
     * Initializes the class properties if necessary.
     */
    protected initializeProperties(): void {
        this.body = this.body || [];
        this.needPaginator = this.needPaginator || false;
        this.totalRowsCount = this.totalRowsCount || 0;
        this.pageSize = this.pageSize || DataGridConfig.pageSize;
        this.pageSizeOptions = this.pageSizeOptions || DataGridConfig.pageSizeOptions;
        this.pageLinkSize = this.pageLinkSize || DataGridConfig.pageLinkSize;
        this.trackByFieldName = this.trackByFieldName || 'id';
        this.adjustedBody = [];
        this.currentPage = 1;
        this.hiddenHead = this.hiddenHead || false;
    }
}